﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using VNet.VisiSuite.Common.Logger;
using VNet.VisiSuite.Common.Production;
using VNet.VisiSuite.Service.Contact.Message.ProductionData;

namespace VNet.VisiSuite.Controls
{

    /// <summary>
    /// Interaction logic for ProductionMeterGraphControl.xaml
    /// </summary>
    public partial class ProductionMeterGraphControl : UserControl
    {

        #region Member variables

        const int ROW_GAP = 2;
        bool _needs_redraw = false;
        bool _needs_update = false;
        List<ProductionMeterDataPoint> _update_items = new List<ProductionMeterDataPoint>();

        #endregion

        public ProductionMeterGraphControl()
        {
            FontSize = FontSize = 10 * 96 / 72;  // 10pt
            InitializeComponent();
        }

        #region Dependency Properties

        public static DependencyProperty StartTimeProperty = DependencyProperty.Register("StartTime", typeof(DateTime), typeof(ProductionMeterGraphControl),
                new PropertyMetadata(onAnyPropertyChanged));

        public DateTime StartTime
        {
            get { return (DateTime)GetValue(StartTimeProperty); }
            set { SetValue(StartTimeProperty, value); }
        }

        public static DependencyProperty EndTimeProperty = DependencyProperty.Register("EndTime", typeof(DateTime), typeof(ProductionMeterGraphControl),
                new PropertyMetadata(onAnyPropertyChanged));

        public DateTime EndTime
        {
            get { return (DateTime)GetValue(EndTimeProperty); }
            set { SetValue(EndTimeProperty, value); }
        }

        // Interval (minutes)
        public static DependencyProperty IntervalProperty = DependencyProperty.Register("Interval", typeof(int), typeof(ProductionMeterGraphControl),
                new PropertyMetadata(60, onAnyPropertyChanged));

        public int Interval
        {
            get { return (int)GetValue(IntervalProperty); }
            set { SetValue(IntervalProperty, value); }
        }

        // TickInterval (minutes)
        public static DependencyProperty TickIntervalProperty = DependencyProperty.Register("TickInterval", typeof(int), typeof(ProductionMeterGraphControl),
                new PropertyMetadata(5, onAnyPropertyChanged));

        public int TickInterval
        {
            get { return (int)GetValue(TickIntervalProperty); }
            set { SetValue(TickIntervalProperty, value); }
        }

        // Legend of colors
        public static DependencyProperty LegendProperty = DependencyProperty.Register("Legend", typeof(List<ProductionMeterGraphLegend>), typeof(ProductionMeterGraphControl),
            new PropertyMetadata(onAnyPropertyChanged));

        public List<ProductionMeterGraphLegend> Legend
        {
            get { return (List<ProductionMeterGraphLegend>)GetValue(LegendProperty); }
            set { SetValue(LegendProperty, value); }
        }

        public static DependencyProperty DatatPointsProperty = DependencyProperty.Register("DataPoints", typeof(ObservableCollection<ProductionMeterDataPoint>), typeof(ProductionMeterGraphControl),
            new PropertyMetadata(onAnyPropertyChanged));

        public ObservableCollection<ProductionMeterDataPoint> DataPoints
        {
            get { return (ObservableCollection<ProductionMeterDataPoint>)GetValue(DatatPointsProperty); }
            set { SetValue(DatatPointsProperty, value); }
        }

        private void onDataPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                case NotifyCollectionChangedAction.Replace:
                    ProductionMeterDataPoint data_item;
                    foreach (var item in e.NewItems)
                    {
                        data_item = item as ProductionMeterDataPoint;
                        if (data_item != null)
                            _update_items.Add(data_item);
                    }
                    _needs_update = true;
                    onNeedsUpdate();
                    break;

                default:
                    break;
            }
        }

        // Generic callback for most properties.
        private static void onAnyPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            var graph = source as ProductionMeterGraphControl;
            graph._needs_redraw = true;
            graph.onNeedsRedraw();

            if (e.Property.Name == "DataPoints")
            {
                var old_collection = e.OldValue as ObservableCollection<ProductionMeterDataPoint>;
                if (old_collection != null)
                    old_collection.CollectionChanged -= graph.onDataPointsChanged;
                var new_collection = e.NewValue as ObservableCollection<ProductionMeterDataPoint>;
                if (new_collection != null)
                    new_collection.CollectionChanged += graph.onDataPointsChanged;
            }

        }

        public static DependencyProperty RedrawErrorProperty = DependencyProperty.Register("RedrawError", typeof(bool), typeof(ProductionMeterGraphControl),
        new PropertyMetadata(onRedrawErrorChanged));

        public bool RedrawError
        {
            get { return (bool)GetValue(RedrawErrorProperty); }
            set { SetValue(RedrawErrorProperty, value); }
        }

        private static void onRedrawErrorChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
        {
            var graph = source as ProductionMeterGraphControl;

        }
        #endregion

        private void onSizeChanged(object sender, SizeChangedEventArgs e)
        {
            _needs_redraw = true;
            onNeedsRedraw();
        }

        private void onNeedsRedraw()
        {
            // Prevent multiple redraws when multiple properties change.
            Dispatcher.BeginInvoke((Action)(() =>
            {
                if (_needs_redraw)
                {
                    _needs_redraw = false;
                    redraw(false);
                    _update_items.Clear();
                }
            }), DispatcherPriority.ContextIdle);
        }

        private void onNeedsUpdate()
        {
            // Prevent multiple redraws when multiple properties change.
            Dispatcher.BeginInvoke((Action)(() =>
            {
                if (_needs_update)
                {
                    _needs_update = false;
                    redraw(true);
                    _update_items.Clear();
                }
            }), DispatcherPriority.ContextIdle);
        }

        private void redraw(bool update)
        {
            if (Visibility != Visibility.Visible)
                return;

            try
            {

                var ww = _canvas.ActualWidth;
                var hh = _canvas.ActualHeight;
                var interval = Interval;

                double top = 0;
                double left = 0;
                double right = ww;
                double bottom = hh - 20;

                double width = right - left;
                double scaling = width / interval;
                double x1, x2;
                double yy;

                DateTime start_time = StartTime;
                DateTime end_time = EndTime;

                int minutes = (int)((TimeSpan)(end_time - start_time)).TotalMinutes;
                int rows = minutes / interval;

                double row_height = (bottom - top - 2 - (rows - 1)) / rows;

                // Data
                IEnumerable<ProductionMeterDataPoint> data;
                if (!update)
                {
                    _canvas.Children.Clear();
                    data = DataPoints;
                }
                else
                {
                    data = _update_items;
                }

                // Grid lines
                Brush brush = new SolidColorBrush(Infragistics.ColorConverter.FromString("#C0C0C0"));
                Brush black = new SolidColorBrush(Infragistics.ColorConverter.FromString("#000000"));
                Line line;
                if (!update)
                {
                    yy = top;
                    for (int ii = 0; ii < rows - 1; ++ii)
                    {
                        yy += row_height + 1;
                        line = new Line();
                        line.X1 = left;
                        line.X2 = right;
                        line.Y1 = yy;
                        line.Y2 = yy;
                        line.StrokeThickness = 1;
                        line.Stroke = brush;
                        line.SnapsToDevicePixels = true;
                        line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
                        line.StrokeEndLineCap = PenLineCap.Square;
                        _canvas.Children.Add(line);
                    }

                    // X-axis tick lines
                    var xx = 0;
                    var tick_interval = TickInterval;
                    while (xx <= interval)
                    {
                        if (xx != 0 && xx != interval)
                        {
                            line = new Line();
                            line.X1 = xx * scaling;
                            line.X2 = xx * scaling;
                            line.Y1 = top + 1;
                            line.Y2 = bottom - 1;
                            line.StrokeThickness = 1;
                            line.Stroke = brush;
                            line.SnapsToDevicePixels = true;
                            line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
                            line.StrokeEndLineCap = PenLineCap.Square;
                            _canvas.Children.Add(line);
                        }

                        TextBlock txt = new TextBlock();
                        txt.FontSize = this.FontSize;
                        txt.FontFamily = this.FontFamily;
                        txt.FontStyle = this.FontStyle;
                        txt.FontWeight = this.FontWeight;
                        txt.Text = xx.ToString();
                        txt.Foreground = this.Foreground;
                        txt.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
                        Canvas.SetLeft(txt, xx * scaling - txt.DesiredSize.Width / 2);
                        Canvas.SetTop(txt, bottom);
                        _canvas.Children.Add(txt);

                        xx += TickInterval;
                    }
                }

                var legend = Legend;
                DateTime row_start = start_time;
                DateTime row_end = row_start.AddMinutes(interval);
                int row_idx = 0;
                yy = top + ROW_GAP;
                double row_hh = row_height - 2.0 * ROW_GAP + 1;
                Rectangle rect;

                DateTime ss, ee;
                if (data != null)
                {
                    foreach (var dp in data)
                    {

                        if (dp.EndTime >= start_time)
                        {
                            ss = dp.StartTime;
                            ee = dp.EndTime;

                            while (ss >= row_end)
                            {
                                row_start = row_end;
                                row_end = row_start.AddMinutes(interval);
                                ++row_idx;
                                yy = top + ROW_GAP + (row_idx * (row_height + 1));
                            }

                            x1 = left + scaling * ((double)((TimeSpan)(ss - row_start)).TotalMinutes);
                            x2 = left + scaling * ((double)((TimeSpan)(ee - row_start)).TotalMinutes);
                            ww = (x2 > right ? right : x2) - x1;

                            if (ww > 0)
                            {
                                rect = new Rectangle();
                                rect.Height = row_hh;
                                rect.Width = ww;
                                Canvas.SetTop(rect, yy);
                                Canvas.SetLeft(rect, x1);
                                rect.Fill = (dp.Legend == - 1) ? Brushes.Transparent : legend[dp.Legend].Color;
                                rect.StrokeThickness = 0;
                                _canvas.Children.Add(rect);
                            }

                            // For items overlapping multiple rows
                            while (x2 > right)
                            {
                                row_start = row_end;
                                row_end = row_start.AddMinutes(interval);
                                ++row_idx;
                                yy = top + ROW_GAP + (row_idx * (row_height + 1));

                                if (yy > bottom)
                                    break;

                                ss = row_start;
                                x1 = left + scaling * ((double)((TimeSpan)(ss - row_start)).TotalMinutes);
                                x2 = left + scaling * ((double)((TimeSpan)(ee - row_start)).TotalMinutes);
                                ww = (x2 > right ? right : x2) - x1;

                                if (ww > 0)
                                {
                                    rect = new Rectangle();
                                    rect.Height = row_hh;
                                    rect.Width = ww;
                                    Canvas.SetTop(rect, yy);
                                    Canvas.SetLeft(rect, x1);
                                    rect.Fill = (dp.Legend == -1) ? Brushes.Transparent : legend[dp.Legend].Color;
                                    rect.StrokeThickness = 0;
                                    _canvas.Children.Add(rect);
                                }
                            }

                        }

                        if (dp.StartTime >= end_time)
                            break;

                    }
                }


                // Border - Axis top, right, bottom, left
                line = new Line();
                line.X1 = left;
                line.X2 = right;
                line.Y1 = top;
                line.Y2 = top;
                line.StrokeThickness = 1;
                line.Stroke = new SolidColorBrush(Infragistics.ColorConverter.FromString("#C0C0C0"));
                line.SnapsToDevicePixels = true;
                line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
                line.StrokeEndLineCap = PenLineCap.Square;
                _canvas.Children.Add(line);

                line = new Line();
                line.X1 = right;
                line.X2 = right;
                line.Y1 = top;
                line.Y2 = bottom;
                line.StrokeThickness = 1;
                line.Stroke = new SolidColorBrush(Infragistics.ColorConverter.FromString("#BFBFBF"));
                line.SnapsToDevicePixels = true;
                line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
                line.StrokeEndLineCap = PenLineCap.Square;
                _canvas.Children.Add(line);

                line = new Line();
                line.X1 = left;
                line.X2 = right;
                line.Y1 = bottom;
                line.Y2 = bottom;
                line.StrokeThickness = 1;
                line.Stroke = new SolidColorBrush(Infragistics.ColorConverter.FromString("#BBBBBB"));
                line.SnapsToDevicePixels = true;
                line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
                line.StrokeEndLineCap = PenLineCap.Square;
                _canvas.Children.Add(line);

                line = new Line();
                line.X1 = left;
                line.X2 = left;
                line.Y1 = top;
                line.Y2 = bottom;
                line.StrokeThickness = 1;
                line.Stroke = new SolidColorBrush(Infragistics.ColorConverter.FromString("#A3A3A3"));
                line.SnapsToDevicePixels = true;
                line.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
                line.StrokeEndLineCap = PenLineCap.Square;
                _canvas.Children.Add(line);

                if (RedrawError == true)
                    RedrawError = false;

            }
            catch (Exception ex)
            {
                RedrawError = true;
                FileLog.Error("redraw() - ", ex);
            }

        }

    }
}
